package domain;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.ImageIcon;

import pagemaker.ExternalProgramException;
import pagemaker.MuPDFPageMaker;

public class PDFdoc implements Comparable<PDFdoc> {
    Set<String>                       tags    = new TreeSet<String>();
    Set<String>                       authors = new TreeSet<String>();
    private int                       currentPage, docNumber;
    private String                    title, pdfFilename, notesFilename;
    private MuPDFPageMaker            pdf;
    private Hashtable<Integer, Notes> notes   = new Hashtable<Integer, Notes>();

    /**
     * @param file
     * @param nbPages
     * @throws ExternalProgramException
     * @throws IOException
     */
    private PDFdoc(String file, int nbPages) {
        try {
            File pdfFile = new File(file);
            pdf = new MuPDFPageMaker(pdfFile, nbPages);
            pdfFilename = file;
            notesFilename = pdfFilename.substring(0, pdfFilename.length() - 3)
                    + "nts";
            title = pdfFile.getName();
            this.currentPage = 1;
        } catch (IOException | ExternalProgramException e) {
            e.printStackTrace();
        }

    }

    private PDFdoc(File file, int nbPages) {
        try {
            pdf = new MuPDFPageMaker(file, nbPages);
            title = file.getName();
            pdfFilename = file.getPath();
            notesFilename = pdfFilename.substring(0, pdfFilename.length() - 3)
                    + "nts";
            this.currentPage = 1;
        } catch (IOException | ExternalProgramException e) {
            e.printStackTrace();
        }
    }

    private PDFdoc(File file) {
        try {
            pdf = new MuPDFPageMaker(file);
            pdfFilename = file.getPath();
            notesFilename = pdfFilename.substring(0, pdfFilename.length() - 3)
                    + "nts";
            title = file.getName();
            this.currentPage = 1;
            loadNotes();
        } catch (IOException | ExternalProgramException e) {
            e.printStackTrace();
        }
    }

    private PDFdoc() {
        super();
    }

    /**
     * Returns an instance of PDFdoc.
     * 
     * @param a
     *            File
     * @return PDFdoc
     * @requires Parameter f is a PDF file that exists on the file system.
     * @throws IOException
     * @throws ExternalProgramException
     */
    public static PDFdoc makePDFdoc(File f) throws IOException,
            ExternalProgramException {
        return new PDFdoc(f);
    }

    /**
     * Returns the set of tags as a string, separated by the given separator.
     * 
     * @param separator
     * @return
     */
    public String tagsToString(String separator) {
        StringBuilder sb = new StringBuilder();
        for (String tag : tags)
            sb.append(tag + ",");
        return sb.substring(0, sb.length() - 1);
    }

    /**
     * Returns the set of authors as a string, separated by the given separator.
     * 
     * @param separator
     * @return
     */
    public String authorsToString(String separator) {
        StringBuilder sb = new StringBuilder();
        for (String tag : authors)
            sb.append(tag + ",");
        return sb.substring(0, sb.length() - 1);
    }

    /**
     * @return the title of the document.
     */
    public String getTitle() {
        return title;
    }

    /**
     * Sets the title of the document.
     * 
     * @param title
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Returns the document number.
     * 
     * @return The number of the document. (unique ID).
     */
    public int getDocumentNumber() {
        return docNumber;
    }

    /**
     * Set the document number.
     * 
     * @param docNumber
     */
    public void setDocumentNumber(int docNumber) {
        this.docNumber = docNumber;
    }

    /**
     * Given a string that contains tags separated by the given separator, adds
     * the tags to the document.
     * 
     * @param tagsString
     */
    public void addTags(String tagsString, String separator) {
        for (String tag : tagsString.toUpperCase().split(separator))
            this.tags.add(tag.trim());
    }

    /**
     * Given a string that contains authors names separated by the given
     * separator, adds the authors to the document.
     * 
     * @param authors
     * @param separator
     */
    public void setAuthors(String authors, String separator) {
        for (String tag : Arrays.asList(authors.toUpperCase().split(separator)))
            this.authors.add(tag.trim());
    }

    /**
     * Returns an ImageIcon that represent the current page.
     * 
     * @return
     * @throws IOException
     * @throws ExternalProgramException
     */
    public ImageIcon getCurrentPage() throws IOException,
            ExternalProgramException {
        return pdf.getPage(currentPage, 20);
    }

    /**
     * Returns an ImageIcon that represent the next page.
     * 
     * @return
     * @throws IOException
     * @throws ExternalProgramException
     * @requires getNbPages() > 0
     */
    public ImageIcon getNextPage() throws IOException, ExternalProgramException {
        if (currentPage < getNumberOfPages()) currentPage++;
        return pdf.getPage(currentPage, 20);
    }

    /**
     * Returns an ImageIcon that represent the previous page.
     * 
     * @return
     * @throws IOException
     * @throws ExternalProgramException
     */
    public ImageIcon getPreviousPage() throws IOException,
            ExternalProgramException {
        if (currentPage > 1) currentPage--;
        return pdf.getPage(currentPage, 20);
    }

    /**
     * Returns the number of pages of the document.
     * 
     * @return
     */
    public int getNumberOfPages() {
        return pdf.getNumberOfPages();
    }

    /**
     * Returns the number of the current page.
     * 
     * @return
     */
    public int getCurrentPageNumber() {
        return currentPage;
    }

    /**
     * Adds a note to a document page.
     * 
     * @param note
     */
    public void addNote(String noteText) {
        int pageNum = currentPage;
        addNote(pageNum, noteText);
    }

    /**
     * Adds a note to the current page.
     * 
     * @param note
     * @param notes
     *            >1 && page < last page
     */
    public void addNote(int pageNum, String noteText) {
        if (notes.containsKey(pageNum)) notes.get(pageNum).addNote(noteText);
        else {
            Notes note = new Notes();
            note.addNote(noteText);
            notes.put(pageNum, note);
        }
        saveNotes();
    }

    /**
     * Gets a note from the current page.
     * 
     * @return note
     */
    public String getNote() {
        int pageNum = currentPage;
        return notes.containsKey(pageNum) ? notes.get(pageNum).showAllNotes()
                : "";
    }

    /**
     * Get all notes from document.
     * 
     * @return all notes
     */
    public String getAllNotes() {
        return getAllNotes(null);
    }

    /**
     * Get all notes from document that contains a <word>.
     * 
     * @param word
     *            search pattern
     * @return all notes containing word
     */
    public String getAllNotes(String word) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i <= getNumberOfPages(); i++) {
            if (notes.containsKey(i)) {
                sb.append("\n---" + (i) + "---\n");
                sb.append(notes.get(i).showAllNotes(word));
            }
        }
        return sb.toString();
    }

    /**
     * Delete a note from current page.
     * 
     * @param noteNum
     *            must exist.
     */
    public void delNote(int noteNum) {
        int pageNum = currentPage;
        notes.get(pageNum).delNote(noteNum);
        if (notes.get(pageNum).isEmpty()) notes.remove(pageNum);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(pdfFilename);
        sb.append(";");
        sb.append(tagsToString(","));
        sb.append(";");
        sb.append(title);
        // sb.append(";");
        // sb.append(authorsToString(","));
        sb.append("\n");
        return sb.toString();
    }

    @Override
    public int compareTo(PDFdoc o) {
        return this.getDocumentNumber() - o.getDocumentNumber();
    }

    /**
     * Load all saved notes.
     */
    @SuppressWarnings("unchecked")
    private void loadNotes() {
        try {
            FileInputStream fis = new FileInputStream(notesFilename);
            ObjectInputStream ois = new ObjectInputStream(fis);
            notes = (Hashtable<Integer, Notes>) ois.readObject();
            ois.close();
        } catch (IOException e) {
            // ignore it...
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Saves notes.
     */
    private void saveNotes() {
        try {
            FileOutputStream fos = new FileOutputStream(notesFilename);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(notes);
            oos.close();
        } catch (IOException e) {
            System.err.println("Ops... unable to create notes file!");
        }
    }
}